package com.landian.commons.jpa.builder;

import com.landian.commons.base.annotation.QueryEqual;
import com.landian.commons.base.hql.Searchable;
import com.landian.commons.jpa.converter.PredicateConverter;
import com.landian.commons.jpa.criteria.Predicable;
import com.landian.commons.jpa.converter.PredicableConverter;
import com.landian.commons.jpa.utils.LikeUtils;
import com.landian.commons.jpa.utils.PathUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.criterion.MatchMode;

import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * 
 * @author caojl
 * @create 2017/11/14
 **/
@Slf4j
public class PredicateFactory {

    /**
     * toPredicate
     * PS: 只适合没有个性化Predicate使用，因为 query.where 只会在最后一次生效
     * @param root
     * @param query
     * @param cb
     * @param searchable
     */
    public static void build(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder cb, Searchable searchable){
        List<Predicate> predicateList = buildFromObject(root, cb, searchable);
        if(!CollectionUtils.isEmpty(predicateList)){
            Predicate[] predicateArr = new Predicate[predicateList.size()];
            for(int i=0; i<predicateList.size(); i++){
                predicateArr[i] = predicateList.get(i);
            }
            query.where(predicateArr);
        }
    }

    /**
     * 构建Predicate数组
     * @param root
     * @param cb
     * @param predicableArr
     * @return
     */
    public static Predicate[] build(Root<?> root, CriteriaBuilder cb, Predicable... predicableArr){
        return build(root,cb,null,predicableArr);
    }

    /**
     * 构建Predicate数组
     * @param root
     * @param cb
     * @param searchable
     * @param predicableArr
     * @return
     */
    public static Predicate[] build(Root<?> root, CriteriaBuilder cb, Searchable searchable, Predicable... predicableArr){
        // 将公共Predicate与定制Predicate汇总
        List<Predicate> list = new ArrayList<>();
        Predicate buildPredicate = null;
        if(null != searchable){
            buildPredicate = build(root, cb, searchable);
        }
        if(null != buildPredicate){
            list.add(buildPredicate);
        }
        List<Predicable> predicableList = PredicableConverter.asList(predicableArr);
        if(CollectionUtils.isNotEmpty(predicableList)){
            predicableList.stream().forEach(predicable -> list.add(predicable.build(root, cb)));
        }
        return PredicateConverter.asPredicateArray(list);
    }

    /**
     * buildPredicate
     * @param root
     * @param cb
     * @param searchable
     * @return
     */
    public static Predicate build(Root<?> root, CriteriaBuilder cb, Searchable searchable){
        List<Predicate> predicateList = buildFromObject(root, cb, searchable);
        if(CollectionUtils.isEmpty(predicateList)){
           return null;
        }
        Predicate[] predicateArr = new Predicate[predicateList.size()];
        for(int i=0; i<predicateList.size(); i++){
            predicateArr[i] = predicateList.get(i);
        }
        Predicate multiPredicate = cb.and(predicateArr);
        return multiPredicate;
    }

    /**
     * 构建Predicate
     * @param root
     * @param cb
     * @param searchable
     * @return
     */
    private static List<Predicate> buildFromObject(Root<?> root, CriteriaBuilder cb, Searchable searchable) {
        List<Predicate> predicateList = new ArrayList();
        Class clazz = searchable.getClass();
        Field[] fields = clazz.getDeclaredFields();
        if(null != fields && fields.length > 0){
            for(Field field : fields){
                //可以访问private
                field.setAccessible(true);
                String fieldName = field.getName();
                //0.忽略透明不需要构建的部分
                if(!PredicateFactoryGuider.needToBuildCriterionField(field, fieldName)){
                    continue;
                }
                Object value = null;
                try {
                    value = field.get(searchable);
                } catch (IllegalAccessException e) {
                    String errorMsg = "构建" + searchable + "属性" + fieldName + "条件异常!！";
                    log.error(errorMsg);
                    log.error(e.getMessage(), e);
                }
                // 1.只针对不为空的值建造查询条件
                if(null != value){
                    Class fieldType = field.getType();
                    // 日期类型特殊处理
                    if (fieldType == Date.class){
                        Predicate predicate = date(root, cb, fieldName, (Date) value);
                        if(null != predicate){
                            predicateList.add(predicate);
                        }
                    }if(fieldType == Integer.class || fieldType == Long.class){
                        Path path = root.get(fieldName);
                        predicateList.add(cb.equal(path,  value));
                    }else if (fieldType == String.class) {
                        Path<String> path = root.get(fieldName);
                        // 判断是否精确查询
                        QueryEqual queryEqual = field.getAnnotation(QueryEqual.class);
                        if(null == queryEqual){
                            predicateList.add(cb.like(path,  LikeUtils.anywhere((String) value)));
                        }else{
                            predicateList.add(cb.like(path,  (String) value));
                        }
                    }
                }
            }
        }
        return predicateList;
    }

    /**
     * 解释日期类型查询Predicate，有待按需求扩展
     * 目前支持字段开头为：ge、le
     * @param root
     * @param cb
     * @param fieldName
     * @param value
     * @return
     */
    private static Predicate date(Root<?> root, CriteriaBuilder cb , String fieldName, Date value) {
        String geKey = "ge";
        String leKey = "le";
        String methodKey = "";
        if(fieldName.startsWith(geKey)){
            methodKey = geKey;
        }else if(fieldName.startsWith(leKey)){
            methodKey = leKey;
        }
        if(StringUtils.isBlank(methodKey)){
            return null;
        }
        String srcField = fieldName.replace(methodKey, "");
        String targetField = PredicateFactoryGuider.lowerFirstChar(srcField);
        Path<Date> path = root.get(targetField);
        if(geKey.equalsIgnoreCase(methodKey)){
            return cb.greaterThanOrEqualTo(path, value);
        }else if(leKey.contains(methodKey)){
            return cb.lessThanOrEqualTo(path, value);
        }else{
            return null;
        }
    }

    /**
     *
     * @param root
     * @param cb
     * @param value
     * @return
     */
    public static Predicate like(Root<?> root, CriteriaBuilder cb, com.querydsl.core.types.Path<?> dslPath, String value) {
        String fieldName = PathUtils.getFieldName(dslPath);
        Path path = root.get(fieldName);
        Predicate predicate = cb.like(path, MatchMode.ANYWHERE.toMatchString(value));
        return predicate;
    }

    /**
     * 构建InPredicate
     * PS：暂时不清楚为什么CriteriaBuilder.in会引起异常
     * @param root
     * @param dslPath
     * @param collection
     * @return
     */
    public static Predicate in(Root<?> root, com.querydsl.core.types.Path<?> dslPath, Collection collection) {
        String fieldName = PathUtils.getFieldName(dslPath);
        Path path = root.get(fieldName);
        Predicate predicate = path.in(collection);
        return predicate;
    }

    /**
     * 构建通用关键字查询Predicate
     * @param root
     * @param cb
     * @param keyWord
     * @param dslPaths
     * @return
     */
    public static Predicate keyWord(Root<?> root, CriteriaBuilder cb, String keyWord, com.querydsl.core.types.Path<?>... dslPaths) {
        if(StringUtils.isBlank(keyWord) || ArrayUtils.isEmpty(dslPaths)){
            return null;
        }
        List<Predicate> list = new ArrayList<>();
        for (com.querydsl.core.types.Path dslPath : dslPaths) {
            if(null != dslPath){
                Predicate predicate = like(root, cb, dslPath, keyWord);
                list.add(predicate);
            }
        }
        if(CollectionUtils.isEmpty(list)){
            return null;
        }
        Predicate[] predicates = PredicateConverter.asPredicateArray(list);
        return cb.or(predicates);
    }

    public static void main(String[] args) {
        Predicate[] predicates = PredicateConverter.asPredicateArray(null, null);
        System.out.println(predicates.length);
    }
}
